package com.mlick.a.leetcode;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * @author xiangxn.li
 * @date 2023/4/28 16:06
 * <a href="https://leetcode.cn/problems/print-foobar-alternately/">...</a>
 * <p>
 * 1115. 交替打印 FooBar
 * 给你一个类：
 * <p>
 * class FooBar {
 * public void foo() {
 * for (int i = 0; i < n; i++) {
 * print("foo");
 * }
 * }
 * <p>
 * public void bar() {
 * for (int i = 0; i < n; i++) {
 * print("bar");
 * }
 * }
 * }
 * 两个不同的线程将会共用一个 FooBar 实例：
 * <p>
 * 线程 A 将会调用 foo() 方法，而
 * 线程 B 将会调用 bar() 方法
 * 请设计修改程序，以确保 "foobar" 被输出 n 次。
 * <p>
 * <p>
 * <p>
 * 示例 1：
 * <p>
 * 输入：n = 1
 * 输出："foobar"
 * 解释：这里有两个线程被异步启动。其中一个调用 foo() 方法, 另一个调用 bar() 方法，"foobar" 将被输出一次。
 * 示例 2：
 * <p>
 * 输入：n = 2
 * 输出："foobarfoobar"
 * 解释："foobar" 将被输出两次。
 * <p>
 * <p>
 * 提示：
 * <p>
 * 1 <= n <= 1000
 * <p>
 * 解析思路: <a href="https://leetcode.cn/problems/print-foobar-alternately/solution/duo-xian-cheng-liu-mai-shen-jian-ni-xue-d220n/">...</a>
 */
public class A1115 {

    static class FooBar1 {
        private int n;

        public FooBar1(int n) {
            this.n = n;
        }

        // 借助 BlockingQueue 队列的阻塞
        BlockingQueue<Integer> foo = new LinkedBlockingQueue<>(1);
        BlockingQueue<Integer> bar = new LinkedBlockingQueue<>(1);

        public void foo(Runnable printFoo) throws InterruptedException {

            for (int i = 0; i < n; i++) {
                foo.put(i);
                // printFoo.run() outputs "foo". Do not change or remove this line.
                printFoo.run();
                bar.put(i);
            }
        }

        public void bar(Runnable printBar) throws InterruptedException {

            for (int i = 0; i < n; i++) {
                bar.take();
                // printBar.run() outputs "bar". Do not change or remove this line.
                printBar.run();
                foo.take();
            }
        }
    }

    static class FooBar2 {
        private int n;

        public FooBar2(int n) {
            this.n = n;
        }

        // 借助 CyclicBarrier 线程屏障等待 + volatile  控制先后
        CyclicBarrier cyclicBarrier = new CyclicBarrier(2);
        volatile boolean flag = true;

        public void foo(Runnable printFoo) throws InterruptedException {

            for (int i = 0; i < n; i++) {
                while (!flag) ;
                // printFoo.run() outputs "foo". Do not change or remove this line.
                printFoo.run();
                flag = false;
                try {
                    cyclicBarrier.await();
                } catch (BrokenBarrierException e) {
                    throw new RuntimeException(e);
                }
            }
        }

        public void bar(Runnable printBar) throws InterruptedException {

            for (int i = 0; i < n; i++) {
                try {
                    cyclicBarrier.await();
                } catch (BrokenBarrierException e) {
                    throw new RuntimeException(e);
                }
                // printBar.run() outputs "bar". Do not change or remove this line.
                printBar.run();
                flag = true;
            }
        }
    }


    public static void main(String[] args) throws InterruptedException {
        // FooBar1 fooBar = new FooBar1(10);
        FooBar2 fooBar = new FooBar2(10);

        Runnable foo = () -> System.out.print("foo");

        Runnable bar = () -> System.out.print("bar");

        new Thread(() -> {
            try {
                fooBar.foo(foo);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }).start();
        new Thread(() -> {
            try {
                fooBar.bar(bar);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }).start();

    }
}
